Chapter 15

Testing and CI/CD

Session 15

Learning Objectives

By the end of this chapter, you will be able to:

1

Testing Pyramid and Scope

Understanding the testing pyramid helps you write effective tests.

Unit Tests

Fast, pure logic (parsers, validators, services without I/O), run in milliseconds.

Widget Tests

Verify a widget's UI and interaction in isolation (pumpWidget, find, tap, pumpAndSettle).

Integration Tests

Full app flows on device/emulator (user journeys, navigation, network stubs).

Rule: Many unit tests, fewer widget tests, minimal but strategic integration tests.

2

Test Setup and Tools

Proper tooling makes testing efficient and reliable.

Testing Tools

  • Use the flutter_test package (included).
  • Use mockito or mocktail for mocks; use test doubles (fakes) when behavior is simple.
  • Use golden tests for visual regressions when UI consistency is critical.
  • Use flutter_driver or integration_test (preferred) for end-to-end tests.

Add Dev Dependencies to pubspec.yaml (Conceptual)

dev_dependencies:
  flutter_test:
  mockito: # or mocktail
  integration_test:
  flutter_lints:
3

Unit Testing Patterns

Following patterns makes unit tests clear and maintainable.

Best Practices

  • Test pure functions and model parsing with a variety of inputs including edge cases.
  • Arrange-Act-Assert structure for clarity.
  • Use setUp/tearDown for shared initialization.

Example Checks (Conceptual)

  • Model.fromJson should parse valid JSON and throw FormatException on invalid JSON.
  • Validator functions return expected error strings or null.

Best Practices

  • Keep tests deterministic and fast.
  • Avoid real network or file I/O; inject services and use mocks/fakes.
4

Widget Testing Basics

Widget tests verify UI behavior and interactions.

Widget Testing Process

  • Pump the widget under test with required providers and dependencies.
  • Use WidgetTester to interact (enterText, tap, pumpAndSettle).
  • Assert visible text, widget counts, widget states, and navigation.

Common Patterns

  • Wrap widgets with MaterialApp when navigation, themes, or localization are required.
  • Provide mocked providers via Provider scope or by injecting fake services.

Example Flow

Pump HomeScreen with test provider returning sample items. Tap first item, await pumpAndSettle(), assert DetailsScreen content visible.

5

Integration Testing Essentials

Integration tests verify complete user journeys.

Integration Testing

  • Write tests using integration_test package; run on emulator or real device.
  • Drive full user journeys: login → navigate → perform action → verify API call or UI reaction.
  • Use network stubs or run a local test server for reliable results.

Runner Pattern

Create an integration_test/main_test.dart that boots the app and uses the WidgetTester-like API to drive interactions.

6

Mocking and Test Doubles

Test doubles help isolate code under test.

Mocks

Assert interactions (method called with arguments). Use mocktail/mockito.

Fakes

Lightweight in-memory implementations (e.g., FakeCacheService using an in-memory map).

Stubs

Return canned responses for specific calls.

Guideline: Prefer fakes for stateful services to keep tests readable and reliable; use mocks when verifying interaction counts or arguments matters.

7

Test Organization and Naming

Well-organized tests are easier to maintain and understand.

Directory Structure

  • test/unit/ — model and service tests
  • test/widget/ — widget tests
  • integration_test/ — end-to-end tests

Naming Conventions

  • Name tests to explain behavior: should_parse_valid_json, shows_error_on_network_failure.
  • Keep test files parallel to lib structure for discoverability (e.g., test/models/course_test.dart).
8

Code Coverage and Metrics

Coverage metrics help identify untested code.

Generate Coverage

  • flutter test --coverage
  • Convert lcov.info to HTML with genhtml (CI artifact).

Set realistic coverage goals (e.g., 70–90%) and prioritize critical logic coverage over trivial getters.

9

Continuous Integration (CI) Basics

CI automates testing and quality checks.

CI Tasks

Static analysis, format check, unit & widget tests, integration tests (optional), build artifacts (apk/aab) for release channels.

CI Providers

Choose CI provider: GitHub Actions, GitLab CI, Bitrise, Codemagic. Example below uses GitHub Actions.

Core CI Steps

  1. Checkout code
  2. Install Flutter (version pinned)
  3. flutter pub get
  4. flutter analyze
  5. flutter test --coverage
  6. Build (optional): flutter build apk --release
  7. Upload artifacts and test reports
10

Example GitHub Actions Workflow (Conceptual)

GitHub Actions provides a straightforward CI/CD solution.

Workflow Configuration

  • Trigger: push, pull_request on main branch.
  • Jobs: analyze-and-test, build (optional), integration-tests (optional on matrix devices).

High-Level Job Steps

  • Set up Flutter
  • Run flutter pub get
  • Run flutter analyze
  • Run flutter test --coverage and upload coverage artifact
  • Build APK and store as artifact

Notes: Use prebuilt actions for Flutter, cache pub packages, and fail fast on lint/test failures.

11

CI Best Practices and Flakiness Reduction

Following best practices ensures reliable CI pipelines.

Best Practices

  • Keep tests deterministic: seed random values, avoid time-based assumptions, and use fake async when necessary.
  • Split slow integration tests into a separate job that runs nightly or only on main branch merges.
  • Cache dependencies to speed up pipelines.
  • Upload test artifacts (logs, coverage, test reports) for debugging failures.
12

Automated Quality Gates

Quality gates enforce code standards automatically.

Quality Gates

  • Enforce static analysis (flutter analyze) and lints via analysis_options.yaml.
  • Fail PR pipelines if tests or analysis fail.
  • Optionally add code coverage threshold checks and block merges when critical coverage drops.
13

Local Developer Workflow

Efficient local workflows catch issues early.

Local Testing

  • Run tests frequently: flutter test
  • Use flutter test --coverage and a local coverage viewer or service to inspect.
  • Use pre-commit hooks to run format and basic checks (dart format, flutter analyze) to catch issues early.
14

Exercises

Practice what you've learned with these exercises:

1. Unit tests for models

Write tests for Course.fromJson with valid, missing fields, and malformed JSON.

2. Widget test for CourseList

Pump a CourseList with a fake provider, assert visible course cards, tap a card, and verify navigation intent.

3. CI pipeline (GitHub Actions)

Create a basic GitHub Actions workflow that runs analyze and tests on PRs. Commit workflow file and verify CI run on push.

15

Session Assignment

Implement and run the three exercises above. Add workflow YAML to repository, ensure the pipeline passes on your branch, and include a short CI results screenshot and test coverage lcov.info as part of your submission.